// Copyright 2020 Google LLC.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/// Script to generate `catalog*.dart` and `test_catalog.dart`.

import 'dart:async' show Future;
import 'dart:io' show File, Platform;
import 'package:path/path.dart' show dirname, join;

final license = '''
// Generated by gen_catalog.dart.
//
// Copyright 2020 Google LLC.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

''';

class Record {
  final int width;
  BigInt poly;
  BigInt init;
  final bool refin;
  final bool refout;
  BigInt xorout;
  BigInt check;
  final int residue;
  final String name;
  final List<String> aliases;

  Record(this.width, int poly, int init, this.refin, this.refout, int xorout,
      int check, this.name,
      {this.residue = 0, this.aliases = const []})
      : poly = BigInt.from(poly).toUnsigned(width),
        init = BigInt.from(init).toUnsigned(width),
        xorout = BigInt.from(xorout).toUnsigned(width),
        check = BigInt.from(check).toUnsigned(width);
}

String pathTo(String directory, String name) {
  return join(
      dirname(dirname(Platform.script.toFilePath(windows: Platform.isWindows))),
      directory,
      name);
}

String recordToClass(Record record, bool useBigInt) {
  final digits = record.width ~/ 4;
  var poly = "0x${record.poly.toRadixString(16).padLeft(digits, '0')}";
  var init = "0x${record.init.toRadixString(16).padLeft(digits, '0')}";
  var mask = "0x${record.xorout.toRadixString(16).padLeft(digits, '0')}";
  if (useBigInt) {
    poly = "BigInt.parse('${poly.substring(2)}', radix: 16)";
    init = "BigInt.parse('${init.substring(2)}', radix: 16)";
    mask = "BigInt.parse('${mask.substring(2)}', radix: 16)";
  }
  return '''
class ${record.name} extends ParametricCrc {
  ${record.name}()
      : super(
          ${record.width},
          $poly,
          $init,
          $mask,
          inputReflected: ${record.refin},
          outputReflected: ${record.refout});
}
''';
}

Future<bool> createLibVM(List<Record> records) async {
  final path = pathTo('lib/src', 'catalog_vm.dart');
  final file = File(path);
  final sink = file.openWrite();
  sink.write(license);
  sink.write('''
/// CRC functions from the catalog at
/// https://reveng.sourceforge.io/crc-catalogue/all.htm.

import 'package:crclib/crclib.dart';

''');
  records.where((r) => r.width > 32).forEach((record) {
    var useBigInt = record.width > 64;
    sink.write('''
${recordToClass(record, useBigInt)}
''');
    record.aliases.forEach((alias) {
      sink.write('''
/// An alias of `${record.name}`.
class $alias extends ${record.name} {}
''');
    });
  });
  await sink.flush();
  await sink.close();
  return Future.value(true);
}

Future<bool> createLibJS(List<Record> records) async {
  final path = pathTo('lib/src', 'catalog_js.dart');
  final file = File(path);
  final sink = file.openWrite();
  sink.write(license);
  sink.write('''
/// CRC functions from the catalog at
/// https://reveng.sourceforge.io/crc-catalogue/all.htm.

import 'package:crclib/crclib.dart';

''');
  records.where((r) => r.width > 32).forEach((record) {
    sink.write('''
${recordToClass(record, true)}
''');
    record.aliases.forEach((alias) {
      sink.write('''
/// An alias of `${record.name}`.
class $alias extends ${record.name} {}
''');
    });
  });
  await sink.flush();
  await sink.close();
  return Future.value(true);
}

Future<bool> createLib(List<Record> records) async {
  final path = pathTo('lib', 'catalog.dart');
  final file = File(path);
  final sink = file.openWrite();
  sink.write(license);
  sink.write('''
/// CRC functions from the catalog at
/// https://reveng.sourceforge.io/crc-catalogue/all.htm.

export 'package:crclib/src/catalog_js.dart'
    if (dart.library.io) 'package:crclib/src/catalog_vm.dart';
import 'package:crclib/crclib.dart';

''');
  records.where((r) => r.width <= 32).forEach((record) {
    sink.write('''
${recordToClass(record, false)}
''');
    record.aliases.forEach((alias) {
      sink.write('''
/// An alias of `${record.name}`.
class $alias extends ${record.name} {}
''');
    });
  });
  await sink.flush();
  await sink.close();
  return await createLibVM(records) && await createLibJS(records);
}

Future<bool> createTest(List<Record> records) async {
  final path = pathTo('test', 'catalog_test.dart');
  final file = File(path);
  final sink = file.openWrite();
  sink.write(license);
  sink.write('''
import 'dart:convert';

import 'package:test/test.dart';

import 'package:crclib/catalog.dart';
import 'package:crclib/src/primitive.dart' show CrcValue, FinalSink;

void main() {
  final input = utf8.encode('123456789');
''');
  records.forEach((record) {
    var check = record.check.toRadixString(16);
    sink.write('''
  test('${record.name}', () {
    expect(${record.name}().convert(input),
           CrcValue(BigInt.parse('$check', radix:16)));
''');
    record.aliases.forEach((alias) {
      sink.write('''
    expect(${alias}().convert(input),
           CrcValue(BigInt.parse('$check', radix:16)));
''');
    });
    sink.write('  });\n');
    sink.write('''
  test('${record.name} clone', () {
    final sink1 = FinalSink();
    final crc1 = ${record.name}().startChunkedConversion(sink1);
    crc1.add('12345'.codeUnits);
    final sink2 = FinalSink();
    final crc2 = crc1.split(sink2);
    crc1.add('6789'.codeUnits);
    crc1.close();
    expect(sink1.value,
           CrcValue(BigInt.parse('$check', radix:16)));
    crc2.add('67890'.codeUnits);
    crc2.close();
    expect(sink2.value,
           ${record.name}().convert('1234567890'.codeUnits));
  });
''');
  });
  sink.write('}\n');
  await sink.flush();
  await sink.close();
  return Future.value(true);
}

Future<int> main() async {
  final records = [
    Record(8, 0x2f, 0xff, false, false, 0xff, 0xdf, 'Crc8Autosar',
        residue: 0x42),
    Record(8, 0xa7, 0x00, true, true, 0x00, 0x26, 'Crc8Bluetooth'),
    Record(8, 0x9b, 0xff, false, false, 0x00, 0xda, 'Crc8Cdma2000'),
    Record(8, 0x39, 0x00, true, true, 0x00, 0x15, 'Crc8Darc'),
    Record(8, 0xd5, 0x00, false, false, 0x00, 0xbc, 'Crc8DvbS2'),
    Record(8, 0x1d, 0x00, false, false, 0x00, 0x37, 'Crc8GsmA'),
    Record(8, 0x49, 0x00, false, false, 0xff, 0x94, 'Crc8GsmB', residue: 0x53),
    Record(8, 0x07, 0x00, false, false, 0x55, 0xa1, 'Crc8I4321',
        residue: 0xac, aliases: ['Crc8Itu']),
    Record(8, 0x1d, 0xfd, false, false, 0x00, 0x7e, 'Crc8ICode'),
    Record(8, 0x9b, 0x00, false, false, 0x00, 0xea, 'Crc8Lte'),
    Record(8, 0x31, 0x00, true, true, 0x00, 0xa1, 'Crc8MaximDow',
        aliases: ['Crc8Maxim', 'Crc8Dow']),
    Record(8, 0x1d, 0xc7, false, false, 0x00, 0x99, 'Crc8MifareMad'),
    Record(8, 0x31, 0xff, false, false, 0x00, 0xf7, 'Crc8Nrsc5'),
    Record(8, 0x2f, 000, false, false, 0x00, 0x3e, 'Crc8OpenSafety'),
    Record(8, 0x07, 0xff, true, true, 0x00, 0xd0, 'Crc8Rohc'),
    Record(8, 0x1d, 0xff, false, false, 0xff, 0x4b, 'Crc8SaeJ1850',
        residue: 0xc4),
    Record(8, 0x07, 0x00, false, false, 0x00, 0xf4, 'Crc8SMBus',
        aliases: ['Crc8']),
    Record(8, 0x1d, 0xff, true, true, 0x00, 0x97, 'Crc8Tech3250',
        aliases: ['Crc8Aes', 'Crc8Ebu']),
    Record(8, 0x9b, 0x00, true, true, 0x00, 0x25, 'Crc8Wcdma'),
    Record(16, 0x8005, 0x00, true, true, 0x00, 0xbb3d, 'Crc16Arc',
        aliases: ['Crc16', 'Crc16Lha', 'Crc16Ibm']),
    Record(16, 0xc867, 0xffff, false, false, 0x00, 0x4c06, 'Crc16Cdma2000'),
    Record(16, 0x8005, 0xffff, false, false, 0x00, 0xaee7, 'Crc16Cms'),
    Record(16, 0x8005, 0x800d, false, false, 0x00, 0x9ecf, 'Crc16Dds110'),
    Record(16, 0x0589, 0x00, false, false, 0x01, 0x7e, 'Crc16DectR',
        residue: 0x0589, aliases: ['Crc16R']),
    Record(16, 0x0589, 0x00, false, false, 0x00, 0x7f, 'Crc16DectX',
        aliases: ['Crc16X']),
    Record(16, 0x3d65, 0x00, true, true, 0xffff, 0xea82, 'Crc16Dnp',
        residue: 0x66c5),
    Record(16, 0x3d65, 0x00, false, false, 0xffff, 0xc2b7, 'Crc16En13757',
        residue: 0xa366),
    Record(16, 0x1021, 0xffff, false, false, 0xffff, 0xd64e, 'Crc16GeniBus',
        residue: 0x1d0f,
        aliases: ['Crc16Darc', 'Crc16Epc', 'Crc16EpcC1g2', 'Crc16ICode']),
    Record(16, 0x1021, 0x00, false, false, 0xffff, 0xce3c, 'Crc16Gsm',
        residue: 0x1d0f),
    Record(16, 0x1021, 0xffff, false, false, 0x00, 0x29b1, 'Crc16Ibm3740',
        aliases: ['Crc16Autosar', 'Crc16CcittFalse']),
    Record(16, 0x1021, 0xffff, true, true, 0xffff, 0x906e, 'Crc16IbmSdlc',
        residue: 0xf0b8,
        aliases: [
          'Crc16IsoHdlc',
          'Crc16IsoIec144433B',
          'Crc16X25',
          'Crc16B',
        ]),
    Record(16, 0x1021, 0xc6c6, true, true, 0x00, 0xbf05, 'Crc16IsoIec144433A',
        aliases: ['Crc16A']),
    Record(16, 0x1021, 0x00, true, true, 0x00, 0x2189, 'Crc16Kermit',
        aliases: ['Crc16Ccitt', 'Crc16CcittTrue', 'Crc16V41Lsb']),
    Record(16, 0x6f63, 0x00, false, false, 0x00, 0xbdf4, 'Crc16LJ1200'),
    Record(16, 0x8005, 0x00, true, true, 0xffff, 0x44c2, 'Crc16MaximDow',
        residue: 0xb0001, aliases: ['Crc16Maxim']),
    Record(16, 0x1021, 0xffff, true, true, 0x00, 0x6f91, 'Crc16Mcrf4xx'),
    Record(16, 0x8005, 0xffff, true, true, 0x00, 0x4b37, 'Crc16Modbus'),
    Record(16, 0x080b, 0xffff, true, true, 0x00, 0xa066, 'Crc16Nrsc5'),
    Record(16, 0x5935, 0x00, false, false, 0x00, 0x5d38, 'Crc16OpenSafetyA'),
    Record(16, 0x755b, 0x00, false, false, 0x00, 0x20fe, 'Crc16OpenSafetyB'),
    Record(16, 0x1dcf, 0xffff, false, false, 0xffff, 0xa819, 'Crc16Profibus',
        residue: 0xe394, aliases: ['Crc16Iec611582']),
    Record(16, 0x1021, 0xb2aa, true, true, 0x00, 0x63d0, 'Crc16Riello'),
    Record(16, 0x1021, 0x1d0f, false, false, 0x00, 0xe5cc, 'Crc16SpiFujitsu',
        aliases: ['Crc16AugCcitt']),
    Record(16, 0x8bb7, 0x00, false, false, 0x00, 0xd0db, 'Crc16T10Dif'),
    Record(16, 0xa097, 0x00, false, false, 0x00, 0x0fb3, 'Crc16Teledisk'),
    Record(16, 0x1021, 0x89ec, true, true, 0x00, 0x26b1, 'Crc16Tms37157'),
    Record(16, 0x8005, 0x00, false, false, 0x00, 0xfee8, 'Crc16Umts',
        aliases: ['Crc16Buypass', 'Crc16Verifone']),
    Record(16, 0x8005, 0xffff, true, true, 0xffff, 0xb4c8, 'Crc16Usb',
        residue: 0xb001),
    Record(16, 0x1021, 0x00, false, false, 0x00, 0x31c3, 'Crc16Xmodem',
        aliases: ['Crc16Acorn', 'Crc16Lte', 'Crc16V41Msb', 'Crc16Zmodem']),
    Record(24, 0x00065b, 0x555555, true, true, 0x00, 0xc25a56, 'Crc24Ble'),
    Record(
        24, 0x5d6dcb, 0xfedcba, false, false, 0x00, 0x7979bd, 'Crc24FlexRayA'),
    Record(
        24, 0x5d6dcb, 0xabcdef, false, false, 0x00, 0x1f23b8, 'Crc24FlexRayB'),
    Record(24, 0x328b63, 0xffffff, false, false, 0xffffff, 0xb4f3e6,
        'Crc24Interlaken',
        residue: 0x144e63),
    Record(24, 0x864cfb, 0x00, false, false, 0x00, 0xcde703, 'Crc24LteA'),
    Record(24, 0x800063, 0x00, false, false, 0x00, 0x23ef52, 'Crc24LteB'),
    Record(24, 0x864cfb, 0xb704ce, false, false, 0x00, 0x21cf02, 'Crc24OpenPgp',
        aliases: ['Crc24']),
    Record(24, 0x800063, 0xffffff, false, false, 0xffffff, 0x200fa5, 'Crc24Os9',
        residue: 0x800fe3),
    Record(32, 0x814141ab, 0x00, false, false, 0x00, 0x3010bf7f, 'Crc32Aixm',
        aliases: ['Crc32Q']),
    Record(32, 0xf4acfb13, 0xffffffff, true, true, 0xffffffff, 0x1697d06a,
        'Crc32Autosar',
        residue: 0x904cddbf),
    Record(32, 0xa833982b, 0xffffffff, true, true, 0xffffffff, 0x87315576,
        'Crc32Base91D',
        residue: 0x45270551, aliases: ['Crc32D']),
    Record(32, 0x04c11db7, 0xffffffff, false, false, 0xffffffff, 0xfc891918,
        'Crc32Bzip2',
        residue: 0xc704dd7b, aliases: ['Crc32Aal5', 'Crc32DectB', 'Crc32B']),
    Record(32, 0x8001801b, 0x00, true, true, 0x00, 0x6ec2edc4, 'Crc32CDRomEdc'),
    Record(32, 0x04c11db7, 0x00, false, false, 0xffffffff, 0x765e7680,
        'Crc32Cksum',
        residue: 0xc704dd7b, aliases: ['Crc32Posix']),
    Record(32, 0x1edc6f41, 0xffffffff, true, true, 0xffffffff, 0xe3069283,
        'Crc32Iscsi', residue: 0xb798b438, aliases: [
      'Crc32Base91C',
      'Crc32Castagnoli',
      'Crc32Interlaken',
      'Crc32C'
    ]),
    Record(32, 0x04c11db7, 0xffffffff, true, true, 0xffffffff, 0xcbf43926,
        'Crc32IsoHdlc',
        residue: 0xdebb20e3,
        aliases: ['Crc32', 'Crc32Adccp', 'Crc32V42', 'Crc32Xz', 'Crc32Pkzip']),
    Record(32, 0x04c11db7, 0xffffffff, true, true, 0x00, 0x340bc6d9,
        'Crc32JamCrc'),
    Record(32, 0x04c11db7, 0xffffffff, false, false, 0x00, 0x0376e6e7,
        'Crc32Mpeg2'),
    Record(32, 0x000000af, 0x00, false, false, 0x00, 0xbd0be338, 'Crc32Xfer'),
    Record(40, 0x0004820009, 0x00, false, false, 0xffffffffff, 0xd4164fc646,
        'Crc40Gsm',
        residue: 0xc4ff8071ff),
    Record(64, 0x42f0e1eba9ea3693, 0x00, false, false, 0x00, 0x6c40df5f0b497347,
        'Crc64Ecma182',
        aliases: ['Crc64']),
    Record(64, 0x000000000000001b, 0xffffffffffffffff, true, true,
        0xffffffffffffffff, 0xb90956c775a41001, 'Crc64GoIso',
        residue: 0x5300000000000000),
    Record(64, 0x42f0e1eba9ea3693, 0xffffffffffffffff, false, false,
        0xffffffffffffffff, 0x62ec59e3f1a4f00a, 'Crc64WE',
        residue: 0xfcacbebd5931a992),
    Record(64, 0x42f0e1eba9ea3693, 0xffffffffffffffff, true, true,
        0xffffffffffffffff, 0x995dc9bbdf1939fa, 'Crc64Xz',
        residue: 0x49958c9abd7d353f, aliases: ['Crc64GoEcma']),
  ];
  final libCreated = await createLib(records);
  if (!libCreated) {
    return Future.value(1);
  }
  final testCreated = await createTest(records);
  if (!testCreated) {
    return Future.value(2);
  }
  return Future.value(0);
}
